package top.miay.snowid;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.Yaml;

import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;

/**
 * @description: snowid-generator.yaml配置加载类
 * @author 猿猿长成记
 */
public class SnowIdGeneratorConfigLoader {
    private static final Logger LOGGER = LoggerFactory.getLogger(SnowIdGeneratorConfigLoader.class);

    public static final String FILE_NAME = "snowid-generator.yaml";

    /**
     * 从classpath加载并检查配置文件
     * */
    public static void loadConfig(){
        try{
            InputStream configStream = SnowIdGeneratorConfig.class.getResourceAsStream("/"+FILE_NAME);
            Yaml yaml = new Yaml();
            SnowIdGeneratorConfig snowIdGeneratorConfig = yaml.loadAs(configStream, SnowIdGeneratorConfig.class);
            checkConfig(snowIdGeneratorConfig);
            initGenerator(snowIdGeneratorConfig);
            try {
                configStream.close();
            } catch (IOException e) {
                LOGGER.warn(e.getMessage(), e);
            }
            LOGGER.info("SnowIdGeneratorConfig: {}", snowIdGeneratorConfig);
        }catch (Exception e){
            LOGGER.error(e.getMessage(), e);
            System.exit(-1);
        }
    }

    private static void checkConfig(SnowIdGeneratorConfig snowIdGeneratorConfig) throws Exception {
        checkBitsSetting(snowIdGeneratorConfig);
        checkDataCenterSetting(snowIdGeneratorConfig);
        checkMachineSetting(snowIdGeneratorConfig);
        checkClockBackTimeSetting(snowIdGeneratorConfig);
        checkEpochTimeSetting(snowIdGeneratorConfig);
    }

    /**
     * 检查雪花分段是否正确
     * */
    private static void checkBitsSetting(SnowIdGeneratorConfig snowIdGeneratorConfig){
        SnowIdGeneratorConfig.AppInfo appInfo = snowIdGeneratorConfig.getAppInfo();
        long length = appInfo.getTimStampBits()+appInfo.getMachineIdBits()+appInfo.getSequenceBits();
        if(length!=64-1){
            throw new SnowIdException("snoid generator config sum(bits)!=63");
        }
    }

    /**
     * 检查数据中心总数与数据中心索引，配置时需连续使用
     * */
    private static void checkDataCenterSetting(SnowIdGeneratorConfig snowIdGeneratorConfig){
        SnowIdGeneratorConfig.AppInfo appInfo = snowIdGeneratorConfig.getAppInfo();
        long dataCenterIndex = appInfo.getDataCenter().getDataCenterIndex();
        if (snowIdGeneratorConfig.getDcCount() <= dataCenterIndex) {
            throw new SnowIdException("snowid generator dataCenter index %s out of bounds %s.", new Object[]{dataCenterIndex, snowIdGeneratorConfig.getDcCount()});
        }else{
            long dcCount = snowIdGeneratorConfig.getDcCount();
            //根据数据中心总量计算偏移量
            long dataCenterBits = 0;
            while(1<<dataCenterBits<dcCount){
                dataCenterBits++;
            }
            appInfo.setDcBits(dataCenterBits);
        }
    }

    /**
     * 检查机器标识是否存在且未超可识别的范围
     * */
    private static void checkMachineSetting(SnowIdGeneratorConfig snowIdGeneratorConfig) throws Exception {
        SnowIdGeneratorConfig.AppInfo appInfo = snowIdGeneratorConfig.getAppInfo();
        long machineId = -1;

        if(appInfo.getRunMode()== SnowIdGeneratorConfig.RunMode.VM){
            SnowIdGeneratorConfig.VMConfig vmConfig = appInfo.getVmConfig();
            if(vmConfig==null){
                throw new SnowIdException("When the application's runMode is configured to '%s', parameter '%s' need to be configured.", new Object[]{"VM", "vmConfig"});
            }
            String ip = SnowIdUtil.getIp(vmConfig.getMachineIpType(), vmConfig.getEth());
            List<SnowIdGeneratorConfig.Machine> machines = vmConfig.getMachines();
            if(machines==null || machines.size()==0){
                throw new SnowIdException("When the application's runMode is configured to '%s', parameter '%s' need to be configured.", new Object[]{"VM", "vmConfig.machines"});
            }
            for(SnowIdGeneratorConfig.Machine machine : machines){
                if(ip.equals(machine.getIp())){
                    machineId = machine.getMachineId();
                    break;
                }
            }
            if(-1 == machineId){
                throw new SnowIdException("The current ip address '%s' does not have a valid machine identifier configured.", new Object[]{ip});
            }
            appInfo.setMachineId(machineId);
        }else{
            SnowIdGeneratorConfig.K8sConfig k8sConfig = appInfo.getK8sConfig();
            if(k8sConfig==null){
                throw new SnowIdException("When the application's runMode is configured to '%s', parameter '%s' need to be configured.", new Object[]{"K8S_CLOUD_STATEFUL_SET", "k8sConfig"});
            }
            String podNameEnvKey = k8sConfig.getPodNameEnvKey();
            String podName = k8sConfig.getPodName();
            if(podNameEnvKey==null || podName==null){
                throw new SnowIdException("When the application's runMode is configured to '%s', parameter '%s' need to be configured.", new Object[]{"K8S_CLOUD_STATEFUL_SET", "k8sConfig.podNameEnvKey and k8sConfig.podName"});
            }
            machineId = Integer.parseInt(podName.substring(podNameEnvKey.length()+1));
        }
        long maxMachineId = (1L <<(appInfo.getMachineIdBits()- appInfo.getDcBits()))-1;
        if(machineId>maxMachineId) {
            throw new SnowIdException("The machineId '%s' exceeds the maximum allowed value '%s'.", new Object[]{machineId, maxMachineId});
        }
        //将机房标识与机器id组合
        long dcBit = appInfo.getDataCenter().getDataCenterIndex()<< (appInfo.getSequenceBits()+appInfo.getMachineIdBits()-appInfo.getDcBits());
        long machineBit = machineId<< appInfo.getSequenceBits();
        long tmpMachineId =  dcBit|machineBit;
        appInfo.setMachineId(tmpMachineId);
    }
    /**
     * 检查支持的时钟回拨参数，需要为2^n，不满足自动调整
     * */
    private static void checkClockBackTimeSetting(SnowIdGeneratorConfig snowIdGeneratorConfig) {
        long clockBackTime = snowIdGeneratorConfig.getClockBackTime();
        int i = 0;
        while (clockBackTime > 1 << i) {
            i++;
        }
        snowIdGeneratorConfig.setClockBackTime(1 << i);
    }

    private static void checkEpochTimeSetting(SnowIdGeneratorConfig snowIdGeneratorConfig) {
        try {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(SnowIdUtil.DATE_TIME_FORMAT);
            LocalDateTime.parse(snowIdGeneratorConfig.getEpochTime(), formatter);
        } catch (Exception e) {
            throw new SnowIdException("snowid generator epochTime init error.", e);
        }
    }

    private static void initGenerator(SnowIdGeneratorConfig snowIdGeneratorConfig){
        SnowIdGenerator.init(snowIdGeneratorConfig);
    }

}
